package com.moyz.adi.common.interfaces;

import com.moyz.adi.common.entity.AiModel;
import com.moyz.adi.common.exception.BaseException;
import com.moyz.adi.common.helper.SSEEmitterHelper;
import com.moyz.adi.common.rag.AdiAiServices;
import com.moyz.adi.common.rag.AdiChatLanguageModelImpl;
import com.moyz.adi.common.util.*;
import com.moyz.adi.common.vo.*;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.net.Proxy;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.List;

import static com.moyz.adi.common.cosntant.AdiConstant.LLM_MAX_INPUT_TOKENS_DEFAULT;
import static com.moyz.adi.common.cosntant.RedisKeyConstant.TOKEN_USAGE_KEY;
import static com.moyz.adi.common.enums.ErrorEnum.A_PARAMS_ERROR;
import static com.moyz.adi.common.enums.ErrorEnum.B_LLM_SERVICE_DISABLED;

/**
 * 抽象语言模型服务基类。
 * <p>
 * 此类提供了一个框架，用于构建特定于不同语言模型平台的服务实现。它包括通用的方法来初始化、配置和使用语言模型，
 * 以及处理与 AI 模型交互所需的基本逻辑。
 *
 * @param <T> 模型平台设置的类型
 */
@Slf4j
public abstract class AbstractLLMService<T> {

    /**
     * HTTP 请求代理设置，默认为 null。
     */
    protected Proxy proxy;

    /**
     * 当前使用的 AI 模型实例。
     */
    @Getter
    protected AiModel aiModel;

    /**
     * 模型平台的具体设置信息。
     */
    protected T modelPlatformSetting;

    /**
     * Redis 操作模板，用于与 Redis 数据库交互。
     */
    protected StringRedisTemplate stringRedisTemplate;

    /**
     * 构造函数，用于初始化 AI 模型和服务设置。
     *
     * @param aiModel       AI 模型实例
     * @param settingName   设置名称，用于从本地缓存中获取配置信息
     * @param clazz         设置信息的类型类
     */
    protected AbstractLLMService(AiModel aiModel, String settingName, Class<T> clazz) {
        this.aiModel = aiModel;
        String st = LocalCache.CONFIGS.get(settingName);
        modelPlatformSetting = JsonUtil.fromJson(st, clazz);

        initMaxInputTokens();
    }

    /**
     * 初始化最大输入 Token 数量，确保其值有效。
     */
    private void initMaxInputTokens() {
        if (this.aiModel.getMaxInputTokens() < 1) {
            this.aiModel.setMaxInputTokens(LLM_MAX_INPUT_TOKENS_DEFAULT);
        }
    }

    /**
     * 获取或初始化 Redis 操作模板。
     *
     * @return Redis 操作模板实例
     */
    private StringRedisTemplate getStringRedisTemplate() {
        if (null == this.stringRedisTemplate) {
            this.stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        }
        return this.stringRedisTemplate;
    }

    /**
     * 设置 HTTP 请求代理。
     *
     * @param proxy 代理设置
     * @return 当前服务实例，支持链式调用
     */
    public AbstractLLMService<T> setProxy(Proxy proxy) {
        this.proxy = proxy;
        return this;
    }

    /**
     * 检测该服务是否可用（不可用的情况通常是没有配置 key）。
     *
     * @return 如果服务可用则返回 true，否则返回 false
     */
    public abstract boolean isEnabled();

    /**
     * 在聊天之前进行参数检查，默认实现总是返回 true。
     *
     * @param params 聊天请求参数
     * @return 如果检查通过则返回 true
     */
    protected boolean checkBeforeChat(SseAskParams params) {
        return true;
    }

    /**
     * 构建一个 ChatLanguageModel 实例，并设置回调以记录 Token 使用情况。
     *
     * @param properties LLM 构建属性
     * @param uuid       唯一标识符，用于 Redis 中的 Token 记录
     * @return 构建好的 ChatLanguageModel 实例
     */
    public ChatLanguageModel buildChatLLM(LLMBuilderProperties properties, String uuid) {
        return new AdiChatLanguageModelImpl(
                doBuildChatLLM(properties), // 构建具体的 ChatLanguageModel 实例
                response -> { // 设置回调函数，在接收到模型响应时执行
                    String redisKey = MessageFormat.format(TOKEN_USAGE_KEY, uuid); // 格式化 Redis 键名
                    getStringRedisTemplate().expire(redisKey, Duration.ofMinutes(10)); // 设置 Redis 键的有效期为 10 分钟

                    // 获取输入和输出 Token 的数量
                    int inputTokenCount = response.tokenUsage().inputTokenCount();
                    int outputTokenCount = response.tokenUsage().outputTokenCount();

                    // 记录日志信息，包括 UUID 和 Token 使用情况
                    log.info("ChatLanguageModel token cost,uuid:{},inputTokenCount:{},outputTokenCount:{}",
                            uuid, inputTokenCount, outputTokenCount);

                    // 将输入和输出 Token 数量推送到 Redis 列表中
                    getStringRedisTemplate().opsForList().rightPushAll(
                            redisKey,
                            String.valueOf(inputTokenCount),
                            String.valueOf(outputTokenCount)
                    );
                }
        );
    }

    /**
     * 实现具体的 ChatLanguageModel 构建逻辑。
     *
     * @param properties LLM 构建属性
     * @return 构建好的 ChatLanguageModel 实例
     */
    protected abstract ChatLanguageModel doBuildChatLLM(LLMBuilderProperties properties);

    /**
     * 构建一个 StreamingChatLanguageModel 实例。
     *
     * @param properties LLM 构建属性
     * @return 构建好的 StreamingChatLanguageModel 实例
     */
    public abstract StreamingChatLanguageModel buildStreamingChatLLM(LLMBuilderProperties properties);

    /**
     * 解析来自 API 的错误响应并转换为自定义异常。
     *
     * @param error 错误对象
     * @return 解析后的 LLMException 异常
     */
    protected abstract LLMException parseError(Object error);

    /**
     * 执行普通聊天，将原始用户问题及历史消息发送给 AI。
     * <p>
     * 该方法负责处理来自用户的聊天请求，并根据配置选择是否使用带有记忆的聊天模式。
     * 如果启用了服务并且请求参数校验通过，则会创建相应的聊天助理并发起聊天请求，
     * 最终通过提供的消费者函数处理AI的回应。
     *
     * @param params   包含聊天请求所需的所有参数的对象，如模型名称、用户消息等
     * @param consumer 接收并处理AI生成的响应、提示元数据和答案元数据的三参数消费者接口
     */
    public void commonChat(SseAskParams params, TriConsumer<String, PromptMeta, AnswerMeta> consumer) {
        // 检查 LLM 服务是否启用，若未启用则抛出异常并记录错误日志
        if (!isEnabled()) {
            log.error("llm service is disabled");
            throw new BaseException(B_LLM_SERVICE_DISABLED);
        }

        // 校验对话参数，若校验不通过则抛出异常并记录错误日志
        if (!checkBeforeChat(params)) {
            log.error("对话参数校验不通过");
            throw new BaseException(A_PARAMS_ERROR);
        }

        // 将图片URL转换为ImageContent对象列表，用于传递给AI模型
        List<ImageContent> imageContents = ImageUtil.urlsToImageContent(params.getAssistantChatParams().getImageUrls());

        // 获取辅助聊天参数
        AssistantChatParams assistantChatParams = params.getAssistantChatParams();

        // 记录当前消息ID的日志信息
        log.info("sseChat,messageId:{}", assistantChatParams.getMessageId());

        TokenStream tokenStream;

        // 判断是否使用带有记忆的聊天模式
        if (StringUtils.isNotBlank(assistantChatParams.getMessageId())) {
            // 定义一个聊天记忆提供者，用于构建具有特定窗口大小的记忆存储
            ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                    .id(memoryId)
                    .maxMessages(6) // 设置最大消息数量为6条
                    .chatMemoryStore(MapDBChatMemoryStore.getSingleton()) // 使用单例模式的MapDB作为聊天记忆存储
                    .build();

            // 构建支持流式输出的聊天助理，并设置聊天记忆提供者
            IChatAssistant assistant = AdiAiServices.builder(IChatAssistant.class, aiModel.getMaxInputTokens())
                    .streamingChatLanguageModel(buildStreamingChatLLM(params.getLlmBuilderProperties()))
                    .chatMemoryProvider(chatMemoryProvider)
                    .build();

            // 根据是否存在系统消息，选择调用不同的聊天方法
            if (StringUtils.isNotBlank(assistantChatParams.getSystemMessage())) {
                tokenStream = assistant.chatWith(
                        assistantChatParams.getMessageId(),
                        assistantChatParams.getSystemMessage(),
                        assistantChatParams.getUserMessage(),
                        imageContents
                );
            } else {
                tokenStream = assistant.chatWithMemory(
                        assistantChatParams.getMessageId(),
                        assistantChatParams.getUserMessage(),
                        imageContents
                );
            }
        } else {
            // 若没有指定消息ID，则使用无记忆的聊天模式
            IChatAssistant assistant = AdiAiServices.builder(IChatAssistant.class, aiModel.getMaxInputTokens())
                    .streamingChatLanguageModel(buildStreamingChatLLM(params.getLlmBuilderProperties()))
                    .build();

            // 同样地，根据是否存在系统消息，选择调用不同的聊天方法
            if (StringUtils.isNotBlank(assistantChatParams.getSystemMessage())) {
                tokenStream = assistant.chatWithSystem(
                        assistantChatParams.getSystemMessage(),
                        assistantChatParams.getUserMessage(),
                        imageContents
                );
            } else {
                tokenStream = assistant.chatSimple(
                        assistantChatParams.getUserMessage(),
                        imageContents
                );
            }
        }

        // 注册回调以处理来自AI的Token流，并将其与SSE事件关联起来
        SSEEmitterHelper.registerTokenStreamCallBack(tokenStream, params, consumer);
    }

}
